package com.rou.im.framework.boot.starter;

import com.rou.im.framework.boot.starter.builder.ProcessorStackConfigureBuilder;
import com.rou.im.framework.boot.starter.postprocessor.ObjectPostProcessor;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * <p></p>
 *
 * @author GaoShuangWen
 * @since 2020-05-26 10:46
 */
public abstract class AbstractConfigurer<B extends ProcessorStackConfigureBuilder> implements Configurer<B> {

    private B securityBuilder;

    private CompositeObjectPostProcessor objectPostProcessor = new CompositeObjectPostProcessor();


    public B and() {
        return getBuilder();
    }
    public B disable() {
        getBuilder().removeConfigurer(getClass());
        return getBuilder();
    }

    protected final B getBuilder() {
        if (securityBuilder == null) {
            throw new IllegalStateException("securityBuilder cannot be null");
        }
        return securityBuilder;
    }


    @SuppressWarnings("unchecked")
    protected <T> T postProcess(T object) {
        return (T) this.objectPostProcessor.postProcess(object);
    }


    public void addObjectPostProcessor(ObjectPostProcessor<?> objectPostProcessor) {
        this.objectPostProcessor.addObjectPostProcessor(objectPostProcessor);
    }


    public void setBuilder(B builder) {
        this.securityBuilder = builder;
    }


    private static final class CompositeObjectPostProcessor implements
            ObjectPostProcessor<Object> {
        private List<ObjectPostProcessor<? extends Object>> postProcessors = new ArrayList<ObjectPostProcessor<?>>();

        @Override
        @SuppressWarnings({ "rawtypes", "unchecked" })
        public Object postProcess(Object object) {
            for (ObjectPostProcessor opp : postProcessors) {
                Class<?> oppClass = opp.getClass();
                Class<?> oppType = GenericTypeResolver.resolveTypeArgument(oppClass,
                        ObjectPostProcessor.class);
                if (oppType == null || oppType.isAssignableFrom(object.getClass())) {
                    object = opp.postProcess(object);
                }
            }
            return object;
        }

        /**
         * Adds an {@link ObjectPostProcessor} to use
         * @param objectPostProcessor the {@link ObjectPostProcessor} to add
         * @return true if the {@link ObjectPostProcessor} was added, else false
         */
        private boolean addObjectPostProcessor(
                ObjectPostProcessor<? extends Object> objectPostProcessor) {
            boolean result = this.postProcessors.add(objectPostProcessor);
            Collections.sort(postProcessors, AnnotationAwareOrderComparator.INSTANCE);
            return result;
        }
    }
}
